
Alkaline.Processing.LexicalContext = function(inheritedContext, BLOCK, builder) {
	var constructor;
	if (inheritedContext) {
		constructor = function() {};
		constructor.prototype = inheritedContext.context;
		this.context = new constructor();
		this.context.length = 0;
	} else
		this.context = { length: 0 };
	this.inheritedContext = inheritedContext;
	this.innerContexts = [];
	if (this.inheritedContext) {
		this.inheritedContext.innerContexts.push(this);
		this.builder = this.inheritedContext.builder;
	} else
		this.builder = builder;
	this.BLOCK = BLOCK;
	this.isFunctionContext = !this.BLOCK.parent || (this.BLOCK.parent.token.type === Alkaline.JSInfo.TOKEN.Function);
	this.varContext = (this.isFunctionContext || !this.inheritedContext) ? this : this.inheritedContext.varContext;
}

Alkaline.Processing.LexicalContext.prototype.addVariable = function(variable) {
	this.context['var' + variable.name] = variable;
	this.context[this.context.length++] = variable;
	variable.immutable = variable.sourceType > 2;
	variable.lexicalContext = this;
}

Alkaline.Processing.LexicalContext.prototype._renameVariable = function(oldName, newName) {
	this.context['var' + newName] = this.context['var' + oldName];
	delete this.context['var' + oldName];
}

Alkaline.Processing.LexicalContext.prototype.removeVariable = function(variable) {
	if (typeof variable === 'string')
		variable = this.getVariable(variable);
	for (var i = 0; i < this.context.length; i++)
		if (this.context[i] === variable) {
			delete this.context['var' + variable.name];
			this.context.length--;	// the actual indexed value is not lost because this is not a native array, it is an object
			// move everything else forward
			while (i < this.context.length) {
				this.context[i] = this.context[i+1];
				i++;
			}
			// delete the leftover variable (either the one removed, or one which was already copied forward)
			delete this.context[this.context.length];
			variable.remove();
			return true;
		}
	return false;
}

Alkaline.Processing.LexicalContext.prototype.getVariable = function(name) {
	return this.context['var' + name] || null;	// the [[context]] property is inherited from the parent and extended
}

Alkaline.Processing.LexicalContext.charactersInVariableNames = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_";
Alkaline.Processing.LexicalContext.getNewVariableNameAvailableInContexts = function(contexts, startFrom) {
	var chars = Alkaline.Processing.LexicalContext.charactersInVariableNames;
	var max = chars.length;
	var current = [-1];
	if (startFrom) {
		for (var i = 0; i < startFrom.length; i++)
			current[i] = chars.indexOf(startFrom.charAt(i));
			if (current[i] < 0) current[i] = 0;
	}

	nextName:
	while (true) {
		current[0]++;
		var i = 0;
		while (current[i] == max) {
			current[i] = 0;
			i++;
			if (i == current.length)
				current[i] = -1;
			current[i]++;
		}
		// compose the variable name
		var name = '';
		for (var i = 0; i < current.length; i++)
			name += chars.charAt(current[i]);
		// check if the variable is used anywhere
		for (var i = 0; i < contexts.length; i++)
			if (contexts[i].getVariable(name))
				continue nextName;
		// no match found. the name is available
		return name;
	}
}

Alkaline.Processing.LexicalContext.prototype.findAllBottomLevelContexts = function() {
	var list = [this];
	var bottom = [];
	for (var i = 0; i < list.length; i++)
		if (list[i].innerContexts.length)
			list.push.apply(list, list[i].innerContexts);
		else
			bottom.push(list[i]);
	return list;
}

Alkaline.Processing.LexicalContext.prototype.getNewVariableAvailableInChildren = function(implied) {
	// make a list of all bottom-level contexts; each inherits all the variables from upstream
	var bottom = this.findAllBottomLevelContexts();
	
	var name = Alkaline.Processing.LexicalContext.getNewVariableNameAvailableInContexts(bottom);
	var token = null, sourceType = implied ? Alkaline.Processing.VariableDefinition.SOURCE_TYPE.IMPLIED : Alkaline.Processing.VariableDefinition.SOURCE_TYPE.DECLARED_VAR;
	if (!implied) {
		token = this.builder.Id(name);
		this._mustHaveVarDecl();
		this.BLOCK.children[0].addChild(token);
	}
	var varObj = new Alkaline.Processing.VariableDefinition(name, sourceType, token);
	this.addVariable(varObj);
	return varObj;
}
Alkaline.Processing.LexicalContext.prototype._mustHaveVarDecl = function() {
	if (this.BLOCK.token.type !== Alkaline.JSInfo.TOKEN.BLOCK) {
		var newBlock = this.builder.replaceTree(this.BLOCK, this.builder.BLOCK([]));
		newBlock.addChild(this.BLOCK);
		this.BLOCK = newBlock;
	}
	var declType = this.isFunctionContext ? Alkaline.JSInfo.TOKEN.Var : Alkaline.JSInfo.TOKEN.Let;
	if (this.BLOCK.children[0].token.type !== declType) {
		var decl;
		if (this.isFunctionContext)
			decl = this.builder.Var();
		else
			decl = this.builder.Let();
		this.builder.addChild(this.BLOCK, decl, 0);
	}
}

Alkaline.Processing.LexicalContext.prototype.getLocalVariables = function() {
	var locals = [];
	for (var i = 0; i < this.context.length; i++)
		locals.push(this.context[i]);
	return locals;
}

Alkaline.Processing.LexicalContext.prototype.resetLocals = function() {
	for (var i = 0; i < this.context.length; i++)
		this.context[i].valueDistribution = this.context[i].startVal;
}

Alkaline.Processing.LexicalContext.prototype.toString = function() {
	var str = '[';
	if (!this.inheritedContext)
		str += 'Top';
	str += 'Context, ';
	str += this.context.length;
	str += ']';
	return str;
}
